#ifndef __GLF_JNI_UTIL_H_INCLUDED__
#error "need to include _jni_util.hpp before jni_code.hpp"
#endif
#include <android/log.h>
#include <vector>

namespace glf {
	// Java to C++
	void AndroidInit();
	void AndroidSetPaths(const char* resourcePath, const char* homePath, const char* tempPath);
	void AndroidStateChanged(bool active);
	void AndroidGetViewSettings();
	void AndroidResizeScreen(int width, int height);
	void AndroidDestroy();
	void AndroidUpdate();
	void AndroidAddTouchEvent(int flag, int x, int y, int id);
	void AndroidOrientationChanged(int angle);
	void AndroidSetNumExtraContext(int num);
	void AndroidAccelerometerEvent(float ax, float ay, float az);

	// C++ to Java
	static JavaVM* sVM = NULL;
	JavaVM* AndroidGetJavaVM(void){return sVM;};

	static jclass sClassGL2JNILib = NULL;
	static jmethodID sGetResourceID = NULL;
	static jmethodID sSetupPathsID = NULL;
	static jmethodID sCreateViewID = NULL;
	static jmethodID sSetViewSettingsID = NULL;
	static jmethodID sSetCurrentContextID = NULL;
	static jmethodID sEnableAccelerometerID = NULL;

	void AndroidOnStartThread(void*) {
		if(!Thread::sIsMain()) {
			JNIEnv* env;
			sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

			sVM->AttachCurrentThread(&env, NULL);

			// Set thread priority
			Thread &currentThread = Thread::GetCurrent();
			currentThread.SetPriority(currentThread.GetPriority());
		}
	}

	void AndroidOnExitThread(void*) {
		if(!Thread::sIsMain()) {
			sVM->DetachCurrentThread();
		}
	}

	bool AndroidGetResource(const char* path, std::vector<char>& buffer) {
		bool result = false;
		
		JNIEnv* env;
		sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

		jstring str = env->NewStringUTF(path);
		jbyteArray mBarray = (jbyteArray)env->CallStaticObjectMethod(sClassGL2JNILib, sGetResourceID, str);
		if(mBarray != NULL) {
			int len = env->GetArrayLength(mBarray);
			buffer.resize(len);
			if(len > 0)
				env->GetByteArrayRegion(mBarray, 0, len, (jbyte*)&buffer[0]);
			env->DeleteLocalRef(mBarray);
			result = true;
		}
		env->DeleteLocalRef(str);
		__android_log_print(ANDROID_LOG_INFO, "", "AndroidGetResource %s (%d bytes)", path, result ? buffer.size() : 0);
		return result;
	}

	void AndroidSetupPaths() {
		JNIEnv* env;
		sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

		env->CallStaticVoidMethod(sClassGL2JNILib, sSetupPathsID);
	}

	void AndroidCreateView() {
		JNIEnv* env;
		sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

		env->CallStaticVoidMethod(sClassGL2JNILib, sCreateViewID);
	}

	void AndroidSetViewSettings(int pixelSize, int depthSize, int stencilSize, int csaa, int depthNonLinear) {
		JNIEnv* env;
		sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

		env->CallStaticVoidMethod(sClassGL2JNILib, sSetViewSettingsID, pixelSize, depthSize, stencilSize, csaa, depthNonLinear);
	}

	bool AndroidSetCurrentContext(int id) {
		JNIEnv* env;
		sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

		return env->CallStaticBooleanMethod(sClassGL2JNILib, sSetCurrentContextID, id);
	}

	void AndroidEnableAccelerometer(bool enable, float frequency) {
		JNIEnv* env;
		sVM->GetEnv((void**)&env, JNI_VERSION_1_2);

		env->CallStaticVoidMethod(sClassGL2JNILib, sEnableAccelerometerID, enable, frequency);
	}
}

extern "C" {
    JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_init(JNIEnv * env, jobject obj);
    JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_initGL(JNIEnv * env, jobject obj);
    JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_stateChanged(JNIEnv * env, jobject obj, jboolean active);
	JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_setPaths(JNIEnv * env, jobject obj, jstring resourcePath, jstring homePath, jstring tempPath);
	JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_getViewSettings(JNIEnv * env, jobject obj);
    JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_resize(JNIEnv * env, jobject obj,  jint width, jint height);
    JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_destroy(JNIEnv * env, jobject obj);
    JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_step(JNIEnv * env, jobject obj);
	JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_touchEvent(JNIEnv * env, jobject obj, jint flag, jint x, jint y , jint id);
	JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_accelerometerEvent(JNIEnv * env, jobject obj, jfloat ax, jfloat ay, jfloat az);
	JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_orientationChanged(JNIEnv * env, jobject obj, jint angle);
	JNIEXPORT jint JNICALL Java_com_gameloft_glf_GL2JNILib_getNumExtraContext(JNIEnv * env, jobject obj);
	JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_setNumExtraContext(JNIEnv * env, jobject obj, jint num);
};

// Called from Main Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_init(JNIEnv * env, jobject obj)
{
	env->GetJavaVM(&glf::sVM);
	glf::sClassGL2JNILib = (jclass)env->NewGlobalRef(obj);
	GLF_ASSERT(glf::sClassGL2JNILib);
	glf::sGetResourceID = env->GetStaticMethodID(glf::sClassGL2JNILib, "getResource", "(Ljava/lang/String;)[B");
	GLF_ASSERT(glf::sGetResourceID);
	glf::sSetupPathsID = env->GetStaticMethodID(glf::sClassGL2JNILib, "setupPaths", "()V");
	GLF_ASSERT(glf::sSetupPathsID);
	glf::sCreateViewID = env->GetStaticMethodID(glf::sClassGL2JNILib, "createView", "()V");
	GLF_ASSERT(glf::sCreateViewID);
	glf::sSetViewSettingsID = env->GetStaticMethodID(glf::sClassGL2JNILib, "setViewSettings", "(IIIII)V");
	GLF_ASSERT(glf::sSetViewSettingsID);
	glf::sSetCurrentContextID = env->GetStaticMethodID(glf::sClassGL2JNILib, "setCurrentContext", "(I)Z");
	GLF_ASSERT(glf::sSetCurrentContextID);
	glf::sEnableAccelerometerID = env->GetStaticMethodID(glf::sClassGL2JNILib, "enableAccelerometer", "(ZF)V");
	GLF_ASSERT(glf::sEnableAccelerometerID);

	glf::AndroidInit();
}

// Called from GL Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_initGL(JNIEnv * env, jobject obj)
{
	// Deprecated
}

// Called from GL Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_stateChanged(JNIEnv * env, jobject obj, jboolean active) {
	glf::AndroidStateChanged(active);
}

// Called from Main Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_setPaths(JNIEnv * env, jobject obj, jstring resourcePath, jstring homePath, jstring tempPath)
{
	glf::ScopeStringChars resourcePathChars(env, resourcePath);
	glf::ScopeStringChars homePathChars(env, homePath);
	glf::ScopeStringChars tempPathChars(env, tempPath);
	glf::AndroidSetPaths(resourcePathChars.get(), homePathChars.get(), tempPathChars.get());
}

// Called from GL Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_getViewSettings(JNIEnv * env, jobject obj)
{
	glf::AndroidGetViewSettings();
}

// Called from GL Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_resize(JNIEnv * env, jobject obj,  jint width, jint height)
{
    glf::AndroidResizeScreen(width, height);
}

// Called from GL Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_destroy(JNIEnv * env, jobject obj)
{
    glf::AndroidDestroy();
}

// Called from GL Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_step(JNIEnv * env, jobject obj)
{
    glf::AndroidUpdate();
}

// Called from UI Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_touchEvent(JNIEnv * env, jobject obj, jint flag, jint x, jint y , jint id)
{
	glf::AndroidAddTouchEvent(flag, x, y, id);
}

// Called from UI Thread
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_orientationChanged(JNIEnv * env, jobject obj, jint angle)
{
	glf::AndroidOrientationChanged(angle);
}

// Called from context factory
JNIEXPORT jint JNICALL Java_com_gameloft_glf_GL2JNILib_getNumExtraContext(JNIEnv * env, jobject obj)
{
	return glf::App::GetInstance()->GetCreationSettings().extraContexts;
}

// Called from context factory
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_setNumExtraContext(JNIEnv * env, jobject obj, jint num)
{
	glf::AndroidSetNumExtraContext(num);
}

// Called from sensor manager event callback
JNIEXPORT void JNICALL Java_com_gameloft_glf_GL2JNILib_accelerometerEvent(JNIEnv * env, jobject obj, jfloat ax, jfloat ay, jfloat az)
{
	//__android_log_print(ANDROID_LOG_INFO, "", "accelerometerEvent %f %f %f", ax, ay, az);
	glf::AndroidAccelerometerEvent(ax, ay, az);
}
